#ifndef G_STRING_INCLUDED
#define G_STRING_INCLUDED

#include "assert.h"

namespace grace_cpp
{
	namespace string
	{
		template<typename T>
		size_t find_zero(const T* buf)
		{
			if (!buf)
				return 0;

			size_t result = 0;

			while (buf[result] != 0)
				++result;

			return result;
		}

		template<typename T>
		size_t find_zero_s(const T* buf, size_t buflen)
		{
			if (!buf)
				return 0;

			if (!buf)
				return 0;

			size_t result = 0;

			while (result < buflen && buf[result] != 0)
				++result;

			return result;
		}

		template<typename T>
		class counted_buffer
		{
		private:
			const T *m_str;
			size_t m_len;
		public:
			counted_buffer(const T* str, size_t len)
			{
				m_str = str;
				m_len = len;
			}

			counted_buffer(const T* str)
			{
				m_str = str;

				/*if (sizeof(T) == sizeof(char))
					m_len = strlen((const char*)str);
				else
					m_len = wcslen((const wchar_t*)str);*/

				m_len = find_zero(str);
			}

			inline const T* GetBuffer() const
			{
				return m_str;
			}

			inline size_t GetLength() const
			{
				return m_len;
			}

			inline operator const T*() const
			{
				return m_str;
			}
		};

		template<typename T, size_t L>
		class static_array
		{
		private:
			T m_str[L + 1];
			size_t m_len;
		public:
			static_array(const T* str, size_t len)
			{
				assign(str, len);
			}

			static_array(const T* str)
			{
				assign(str, find_zero(str));
			}

			static_array()
			{
				m_len = 0;
				m_str[0] = 0;
			}

			static_array(const static_array &src)
			{
				assign(src.GetBuffer(), src.GetLength());
			}

			static_array(const counted_buffer<T> &str)
			{
				assign(str.GetBuffer(), str.GetLength());
			}

			void assign(const T* str, size_t len)
			{
				if (len > L)
					m_len = L;
				else 
					m_len = len;

				if (m_len)
					memcpy(m_str, str, sizeof(T) * m_len);

				m_str[m_len] = 0;
			}

			inline const T* GetBuffer() const
			{
				return m_str;
			}

			inline size_t GetLength() const
			{
				return m_len;
			}

			inline operator T*()
			{
				return m_str;
			}

			static_array& operator=(const T* str)
			{
				/*
				size_t len;

				if (str)
				{
					
					if (sizeof(T) == sizeof(char))
						len = strlen((const char*)str);
					else
						len = wcslen((const wchar_t*)str);
						

					len = ;
				}
				else
					len = 0;
				*/

				assign(str, find_zero(str));

				return *this;
			}

			static_array& operator=(const static_array &src)
			{
				assign(src.GetBuffer(), src.GetLength());
			}

			void append(const T* tail, size_t len)
			{
				size_t rem = L - m_len;

				if (len > rem)
					assert(false);
				else
				{
					memcpy(m_str + m_len, tail, len * sizeof(T));

					m_len += len;

					m_str[m_len] = 0;
				}
			}

			void append(const counted_buffer<T> &tail)
			{
				append(tail.GetBuffer(), tail.GetLength());
			}

			void AppendPath(const T* buf, size_t len)
			{
				if (!buf || !len)
					return;

				if (PATH_DELIMITER == buf[0])
				{
					++buf;
					--len;
				}

				if (PATH_DELIMITER == buf[len - 1])
					--len;

				if (!len)
					return;

				bool flag;

				if (m_len && PATH_DELIMITER != m_str[m_len - 1])
				{
					m_str[m_len++] = PATH_DELIMITER;
					flag = true;
				}
				else flag = false;

				size_t rem = L - m_len;

				if (len > rem)
				{
					if (flag)
						m_str[--m_len] = 0;
					return;
				}

				memcpy(m_str + m_len, buf, len * sizeof(T));

				m_len += len;

				m_str[m_len] = 0;
			}

			void AppendPath(const counted_buffer<T> &tail)
			{
				AppendPath(tail.GetBuffer(), tail.GetLength());
			}

			inline void SetLength(size_t len)
			{
				if (len <= L)
				{
					m_len = len;
					m_str[m_len] = 0;
				}
			}

			void GetSubDir(static_array *subdir, bool exclude = false)
			{
				if (!m_len)
				{
					if (subdir)
						subdir->SetLength(0);

					return;
				}

				if (PATH_DELIMITER == m_str[m_len - 1])
				{
					m_str[m_len - 1] = 0;
					--m_len;
				}

				size_t i = m_len;

				while(i && (PATH_DELIMITER != m_str[i - 1])) --i;

				if (i)
				{
					if (subdir)
						subdir->assign(m_str + i, m_len - i);

					if (exclude)
						SetLength(--i);
				}
				else if (subdir)
					subdir->SetLength(0);
			}

			void ExtractSubDir(static_array &subdir)
			{
				GetSubDir(&subdir, true);
			}

			static_array& ParentDir()
			{
				GetSubDir(NULL, true);
				return (*this);
			}

		};

		template <typename T>
		class dynamic_array
		{
		private:
			typedef struct dynamic_array_data
			{
				LONG nReference;
				size_t nElement;
				T* pData;
			}dynamic_array_data;

			dynamic_array_data *m_data;

		protected:

			inline LONG AddRef()
			{
				if (m_data)
					return InterlockedIncrement(&(m_data->nReference));
				else
					return 0;
			}

			inline void release()
			{
				if (m_data)
				{
					if (!InterlockedDecrement(&(m_data->nReference)))
					{
						if (m_data->pData)
							delete [] (m_data->pData);

						free(m_data);

						m_data = NULL;
					}
				}
			}

			inline T* GetWritableBuffer() const
			{
				if (m_data)
					return m_data->pData;
				else
					return NULL;
			}

		public:
			class dynamic_array_element
			{
			private:
				dynamic_array *m_owner;
				size_t m_index;
			public:
				dynamic_array_element(dynamic_array * str, size_t index)
				{
					m_owner = str;
					m_index = index;
				};

				inline operator T()
				{
					return m_owner->ElementAt(m_index); 
				}

				inline void operator=(const T& c)
				{
					m_owner->ElementAt(m_index, c);
				}

				inline T operator*()
				{
					return m_owner->ElementAt(m_index); 
				}

				inline T value()
				{
					return m_owner->ElementAt(m_index); 
				}
			};

		public:
			dynamic_array():m_data(NULL){}

			dynamic_array(const dynamic_array& src)
			{
				m_data = src.m_data;
				AddRef();	
			}

			dynamic_array(const T* s): m_data(NULL)
			{   
				if (s)
					SetBuffer(s, find_zero(s));
				else
					SetBuffer(NULL, 0);
			}

			dynamic_array(const T* s, size_t size):m_data(NULL)
			{
				SetBuffer(s, size);
			}

			virtual ~dynamic_array()
			{
				release();
			}

			inline const T* GetBuffer() const 
			{
				if (m_data)
					return m_data->pData;
				else
					return NULL;
			}

			inline size_t GetLength() const 
			{
				if (m_data)
					return m_data->nElement;
				else 
					return 0;
			}

			inline T ElementAt(size_t index) const
			{
				return GetBuffer()[index];
			}

			inline void ElementAt(size_t index, const T& c)
			{
				const T* s = GetBuffer();

				if (s[index] != c)
				{
					if (m_data->nReference != 1)
						*this = dynamic_array(s, GetLength());

					m_data->pData[index] = c;
				}
			}

			inline void CheckIndex(size_t index) const
			{
				size_t size = GetLength();
				if (index >= size)
				{
					char msg[256];
					sprintf_s(msg, "GetLength=%u, index=%u", array_length(msg), size, index);
					throw msg;
				}
			}

			inline dynamic_array_element operator[](size_t index) 
			{		
				CheckIndex(index);
				return dynamic_array_element(this, index);
			}

			void SetBuffer(const T* s, size_t size)
			{
				T* buf = GetWritableBuffer();

				if (s && size && s != buf)
				{
					if (GetLength() == size && 1 == m_data->nReference)
					{
						for(size_t i = 0; i < size; ++i)
							buf[i] = s[i];
					}
					else
					{
						release();
						m_data = new dynamic_array_data;
						m_data->nReference = 1;
						m_data->nElement = size;
						buf = new T[size + 1];
						m_data->pData = buf;

						for(size_t i = 0; i < size; ++i)
							buf[i] = s[i];

						buf[size] = 0;
					}
				}
			}

			void append(const T* s, size_t size)
			{
				if (s && size)
				{
					size_t mysize = GetLength();

					T* buf = new T[size + mysize + 1];

					size_t i;

					for(i = 0; i < mysize; ++i)
						buf[i] = m_data->pData[i];

					for(i = 0; i < size; ++i)
						buf[i + mysize] = s[i];

					buf[size + mysize] = 0;

					if (m_data)
					{
						if (m_data->nReference == 1)
							delete [] (m_data->pData);
						else
						{
							release();
							m_data = new dynamic_array_data();
						}
					}
					else
						m_data = new dynamic_array_data();


					m_data->nReference = 1;
					m_data->nElement = size + mysize;
					m_data->pData = buf;
				}
			}

			dynamic_array& operator=(const dynamic_array &src)
			{
				release();
				m_data = src.m_data;
				AddRef();
				return (*this);
			}

			dynamic_array& operator=(const T* s)
			{
				if (s)
					SetBuffer(s, find_zero(s));
				else 
					SetBuffer(s, 0);

				return (*this);
			}

			dynamic_array operator+(const dynamic_array &tail)
			{
				dynamic_array tmp(*this);
				tmp.append(tail.GetBuffer(), tail.GetLength());
				return tmp;
			}

			dynamic_array& operator+=(const dynamic_array &tail)
			{
				dynamic_array tmp(*this);
				tmp.append(tail.GetBuffer(), tail.GetLength());
				(*this) = tmp;
				return (*this);
			}

			dynamic_array operator+(const T* s)
			{
				dynamic_array tmp(*this);
				append(s, find_zero(s));
				return tmp;
			}

			dynamic_array& operator+=(const T* s)
			{
				dynamic_array tmp(*this);
				tmp.append(s, find_zero(s));
				(*this) = tmp;
				return (*this);
			}

			dynamic_array copy(size_t StartIndex, size_t size) const
			{
				size_t mysize = GetLength();

				if (StartIndex >= mysize)
					return dynamic_array();

				if (size > mysize - StartIndex)
					size = mysize - StartIndex;

				const char* T = GetBuffer();
				return dynamic_array(s + StartIndex, size);
			}

			dynamic_array& remove(size_t StartIndex, size_t size)
			{
				size_t mysize = GetLength();

				if (StartIndex < mysize)
				{
					if (size > mysize - StartIndex)
						size = mysize - StartIndex;

					if (size == mysize)
						release();

					const T* s = GetBuffer();

					T* buf = new T[mysize - size + 1];

					size_t i, j;

					for(i = 0; i < StartIndex; ++i)
						buf[i] = s[i];

					j = StartIndex + size;

					for(; i < mysize - size; ++i, ++j)
						buf[i] = s[j];

					SetBuffer(buf, mysize - size);
				}

				return (*this);
			}

		};

	}
}

#endif